The goals / steps of this project are the following:
1 - Compute the camera calibration matrix and distortion coefficients given a set of chessboard images.
2 - Apply a distortion correction to raw images.
3 - Use color transforms, gradients, etc., to create a thresholded binary image.
4 - Apply a perspective transform to rectify binary image ("birds-eye view").
5 - Detect lane pixels and fit to find the lane boundary.
6 - Determine the curvature of the lane and vehicle position with respect to center.
7 - Warp the detected lane boundaries back onto the original image.
8 - Output visual display of the lane boundaries and numerical estimation of lane curvature and vehicle position.
import numpy as np
import cv2
import glob
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
import pickle
import os
from moviepy.editor import VideoFileClip
%matplotlib inline
def Calculate_Sobel(warpedimage, orient, thresh_min=0, thresh_max=255, kernelSize=3):
# 1) Convert to gray scale
gray = cv2.cvtColor(warpedimage, cv2.COLOR_RGB2GRAY)
# 2) Take the gradient in x and y separately
sobelx = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=kernelSize)
sobely = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=kernelSize)
# 3) Take the absolute value of the x and y gradients
abs_sobelx = np.absolute(sobelx)
abs_sobely = np.absolute(sobely)
abs_sobelxy = np.sqrt(sobelx**2 + sobely**2)
# 4) Get gradient as per orientation passed
if orient=='x':
grad = abs_sobelx
elif orient == 'y':
grad = abs_sobely
elif orient == 'xy':
grad= abs_sobelxy
elif orient == 'dir':
grad = np.arctan2(abs_sobely, abs_sobelx)
# 5) Scale to 8-bit (0 - 255) then convert to type = np.uint8
scaled_sobel = np.uint8(255*grad/np.max(grad))
# 6) Create a mask of 1's where the scaled gradient magnitude
# is > thresh_min and < thresh_max
binary_output = np.zeros_like(scaled_sobel)
binary_output[(scaled_sobel >= thresh_min) & (scaled_sobel <= thresh_max)] = 1
# 7) Return this mask as your binary_output image
return binary_output
def hls_threshold(img, channel, thresh=(0, 255)):
# Convert from RGB to HLS
hls = cv2.cvtColor(img, cv2.COLOR_RGB2HLS)
# Extract channels needed H/L/S from the image
if channel == 'h':
channelImg = hls[:,:,0]
elif channel == 'l':
channelImg = hls[:,:,1]
elif channel == 's':
channelImg = hls[:,:,2]
hlsBinary = np.zeros_like(channelImg)
hlsBinary[(channelImg > thresh[0]) & (channelImg <= thresh[1])] = 1
return hlsBinary
def region_of_interest(img):
mask = np.zeros_like(img)
imshape = img.shape
if len(imshape) > 2:
channel_count = img.shape[2]
ignore_mask_color = (255,) * channel_count
else:
ignore_mask_color = 255
# Define the vertices of region of interest
vertices = np.array([[(0,imshape[0]),(imshape[1]*0.48, imshape[0]*0.58), (imshape[1]*0.52, imshape[0]*0.58), (imshape[1],imshape[0])]], dtype=np.int32)
cv2.fillPoly(mask, vertices, ignore_mask_color)
masked_image = cv2.bitwise_and(img, mask)
return masked_image
def find_lane_pixels_blind(binary_warped):
# HYPERPARAMETERS
# Choose the number of sliding windows
nwindows = 9
# Set the width of the windows +/- margin
margin = 100
# Set minimum number of pixels found to recenter window
minpix = 50
# Take a histogram of the bottom half of the image
histogram = np.sum(binary_warped[binary_warped.shape[0]//2:,:], axis=0)
# Create an output image to draw on and visualize the result
out_img = np.dstack((binary_warped, binary_warped, binary_warped))*255
# Find the peak of the left and right halves of the histogram
# These will be the starting point for the left and right lines
midpoint = np.int(histogram.shape[0]//2)
leftx_base = np.argmax(histogram[:midpoint])
rightx_base = np.argmax(histogram[midpoint:]) + midpoint
# Set height of windows - based on nwindows above and image shape
window_height = np.int(binary_warped.shape[0]//nwindows)
# Identify the x and y positions of all nonzero pixels in the image
nonzero = binary_warped.nonzero()
nonzeroy = np.array(nonzero[0])
nonzerox = np.array(nonzero[1])
# Current positions to be updated later for each window in nwindows
leftx_current = leftx_base
rightx_current = rightx_base
# Create empty lists to receive left and right lane pixel indices
left_lane_inds = []
right_lane_inds = []
# Step through the windows one by one
for window in range(nwindows):
# Identify window boundaries in x and y (and right and left)
win_y_low = binary_warped.shape[0] - (window+1)*window_height
win_y_high = binary_warped.shape[0] - window*window_height
win_xleft_low = leftx_current - margin
win_xleft_high = leftx_current + margin
win_xright_low = rightx_current - margin
win_xright_high = rightx_current + margin
# Draw the windows on the visualization image
#cv2.rectangle(out_img,(win_xleft_low,win_y_low),
#(win_xleft_high,win_y_high),(0,255,0), 5)
#cv2.rectangle(out_img,(win_xright_low,win_y_low),
#(win_xright_high,win_y_high),(0,255,0), 5)
# Identify the nonzero pixels in x and y within the window
good_left_inds = ((nonzeroy >= win_y_low) & (nonzeroy < win_y_high) &
(nonzerox >= win_xleft_low) & (nonzerox < win_xleft_high)).nonzero()[0]
good_right_inds = ((nonzeroy >= win_y_low) & (nonzeroy < win_y_high) &
(nonzerox >= win_xright_low) & (nonzerox < win_xright_high)).nonzero()[0]
# Append these indices to the lists
left_lane_inds.append(good_left_inds)
right_lane_inds.append(good_right_inds)
# If you found number of indices > minpix parameter, recenter next window on their mean position
if len(good_left_inds) > minpix:
leftx_current = np.int(np.mean(nonzerox[good_left_inds]))
if len(good_right_inds) > minpix:
rightx_current = np.int(np.mean(nonzerox[good_right_inds]))
# Concatenate the arrays of indices (previously was a list of lists of pixels)
try:
left_lane_inds = np.concatenate(left_lane_inds)
right_lane_inds = np.concatenate(right_lane_inds)
except ValueError:
# Avoids an error if the above is not implemented fully
pass
# Extract left and right line pixel positions
leftx = nonzerox[left_lane_inds]
lefty = nonzeroy[left_lane_inds]
rightx = nonzerox[right_lane_inds]
righty = nonzeroy[right_lane_inds]
# Fit a second order polynomial to each using `np.polyfit`
left_fit = np.polyfit(lefty, leftx, 2)
right_fit = np.polyfit(righty, rightx, 2)
return leftx, lefty, rightx, righty, out_img, left_fit, right_fit
def search_around_poly(binary_warped,left_fit, right_fit):
# HYPERPARAMETER
# Choose the width of the margin around the previous polynomial to search around it
margin = 100
# Grab activated pixels
nonzero = binary_warped.nonzero()
nonzeroy = np.array(nonzero[0])
nonzerox = np.array(nonzero[1])
# Set the area of search based on activated x-values
# within the +/- margin of our polynomial function
left_lane_inds = ((nonzerox > (left_fit[0]*(nonzeroy**2) + left_fit[1]*nonzeroy +
left_fit[2] - margin)) & (nonzerox < (left_fit[0]*(nonzeroy**2) +
left_fit[1]*nonzeroy + left_fit[2] + margin)))
right_lane_inds = ((nonzerox > (right_fit[0]*(nonzeroy**2) + right_fit[1]*nonzeroy +
right_fit[2] - margin)) & (nonzerox < (right_fit[0]*(nonzeroy**2) +
right_fit[1]*nonzeroy + right_fit[2] + margin)))
# Extract left and right line pixel positions
leftx = nonzerox[left_lane_inds]
lefty = nonzeroy[left_lane_inds]
rightx = nonzerox[right_lane_inds]
righty = nonzeroy[right_lane_inds]
# Fit a second order polynomial to each with np.polyfit()
left_fit = np.polyfit(lefty, leftx, 2)
right_fit = np.polyfit(righty, rightx, 2)
# Generate x and y values for plotting
ploty = np.linspace(0, binary_warped.shape[0]-1, binary_warped.shape[0])
# Calculate both polynomials using ploty, left_fit and right_fit
left_fitx = left_fit[0]*ploty**2 + left_fit[1]*ploty + left_fit[2]
right_fitx = right_fit[0]*ploty**2 + right_fit[1]*ploty + right_fit[2]
## Visualization ##
# Create an image to draw on and an image to show the selection window
out_img = np.dstack((binary_warped, binary_warped, binary_warped))*255
window_img = np.zeros_like(out_img)
# Color in left and right line pixels
out_img[nonzeroy[left_lane_inds], nonzerox[left_lane_inds]] = [255, 0, 0]
out_img[nonzeroy[right_lane_inds], nonzerox[right_lane_inds]] = [0, 0, 255]
# Generate a polygon to illustrate the search window area
# And recast the x and y points into usable format for cv2.fillPoly()
left_line_window1 = np.array([np.transpose(np.vstack([left_fitx-margin, ploty]))])
left_line_window2 = np.array([np.flipud(np.transpose(np.vstack([left_fitx+margin,
ploty])))])
left_line_pts = np.hstack((left_line_window1, left_line_window2))
right_line_window1 = np.array([np.transpose(np.vstack([right_fitx-margin, ploty]))])
right_line_window2 = np.array([np.flipud(np.transpose(np.vstack([right_fitx+margin,
ploty])))])
right_line_pts = np.hstack((right_line_window1, right_line_window2))
# Draw the lane onto the warped blank image
cv2.fillPoly(window_img, np.int_([left_line_pts]), (0,255, 0))
cv2.fillPoly(window_img, np.int_([right_line_pts]), (0,255, 0))
result = cv2.addWeighted(out_img, 1, window_img, 0.3, 0)
return leftx, lefty, rightx, righty, result, left_fit,right_fit
def fit_polynomial(binary_warped,is_First_Frame=True,left_fit=0,right_fit=0):
if (is_First_Frame == True):
# First frame so search blindly for lane lines
leftx, lefty, rightx, righty, out_img, left_fit, right_fit = find_lane_pixels_blind(binary_warped)
else:
# In case of video we already have history of where can we search for lane lines
leftx, lefty, rightx, righty, out_img, left_fit, right_fit = search_around_poly(binary_warped,left_fit,right_fit)
# Generate x and y values for plotting
ploty = np.linspace(0, binary_warped.shape[0]-1, binary_warped.shape[0] )
try:
left_fitx = left_fit[0]*ploty**2 + left_fit[1]*ploty + left_fit[2]
right_fitx = right_fit[0]*ploty**2 + right_fit[1]*ploty + right_fit[2]
except TypeError:
# Avoids an error if `left` and `right_fit` are still none or incorrect
print('The function failed to fit a line!')
left_fitx = 1*ploty**2 + 1*ploty
right_fitx = 1*ploty**2 + 1*ploty
## Visualization ##
# Colors in the left and right lane regions
out_img[lefty, leftx] = [255, 0, 0]
out_img[righty, rightx] = [0, 0, 255]
return out_img, left_fitx, right_fitx, ploty, left_fit, right_fit
def draw_lanes(image,warped, left_fitx, right_fitx, ploty, Minv):
# Create an image to draw the lines on
warp_zero = np.zeros_like(warped).astype(np.uint8)
color_warp = np.dstack((warp_zero, warp_zero, warp_zero))
# Recast the x and y points into usable format for cv2.fillPoly()
pts_left = np.array([np.transpose(np.vstack([left_fitx, ploty]))])
pts_right = np.array([np.flipud(np.transpose(np.vstack([right_fitx, ploty])))])
pts = np.hstack((pts_left, pts_right))
# Draw the lane onto the warped blank image
cv2.fillPoly(color_warp, np.int_([pts]), (0,255, 0))
# Warp the blank back to original image space using inverse perspective matrix (Minv)
newwarp = cv2.warpPerspective(color_warp, Minv, (image.shape[1], image.shape[0]))
return newwarp
def measure_curvature_pixels(img_width, leftx, rightx, ploty):
'''
Calculates the curvature of polynomial functions in pixels.
'''
# Define conversions in x and y from pixels space to meters
ym_per_pix = 30/720 # meters per pixel in y dimension
xm_per_pix = 3.7/700 # meters per pixel in x dimension
# Define y-value where we want radius of curvature
# We'll choose the maximum y-value, corresponding to the bottom of the image
y_eval = np.max(ploty)
left_fit_cr = np.polyfit(ploty*ym_per_pix, leftx*xm_per_pix, 2)
right_fit_cr = np.polyfit(ploty*ym_per_pix, rightx*xm_per_pix, 2)
# Calculation of R_curve (radius of curvature)
left_curverad = ((1 + (2*left_fit_cr[0]*y_eval*ym_per_pix + left_fit_cr[1])**2)**1.5) / np.absolute(2*left_fit_cr[0])
right_curverad = ((1 + (2*right_fit_cr[0]*y_eval*ym_per_pix + right_fit_cr[1])**2)**1.5) / np.absolute(2*right_fit_cr[0])
curverad = int((left_curverad + right_curverad) / 2)
# Calculate left lane position
left_lane_pos = left_fit_cr[0]*(y_eval*ym_per_pix)**2 + left_fit_cr[1]*y_eval*ym_per_pix + left_fit_cr[2]
right_lane_pos = right_fit_cr[0]*(y_eval*ym_per_pix)**2 + right_fit_cr[1]*y_eval*ym_per_pix + right_fit_cr[2]
# Calculate middle point of 2 lanes
mid_lane_pos = (left_lane_pos+right_lane_pos)/2
img_center = int(img_width/2) * xm_per_pix
# Approximate to 2 digts, if car_pos this means car offset is to the right
# of the center, otherwise it is to the left of the center
car_pos = int((mid_lane_pos - img_center)*100+0.5) / 100
return curverad, car_pos
def print_statistics_to_image(final_image, curvature, car_offset):
# Build the curvature text with the curvature passed to the function
curvead_text = "Radius of Curvature : " + str(curvature) + "(m)"
# Build the car offset text with the offset passed to the function
if car_offset < 0:
offset_text = "Vehicle is " + str(car_offset * -1) + "m left of the center"
else:
offset_text = "Vehicle is " + str(car_offset) + "m right of the center"
# Add the text on top of the image
font = cv2.FONT_HERSHEY_DUPLEX
cv2.putText(final_image,curvead_text,(10,40), font, 1,(255,0,0),2,cv2.LINE_AA)
cv2.putText(final_image,offset_text,(10,75), font, 1,(255,0,0),2,cv2.LINE_AA)
nx = 9
ny = 6
# prepare object points, like (0,0,0), (1,0,0), (2,0,0) ....,(6,5,0)
objp = np.zeros((6*9,3), np.float32)
objp[:,:2] = np.mgrid[0:9,0:6].T.reshape(-1,2)
# Arrays to store object points and image points from all the images.
# 3d points in real world space
objpoints = []
# 2d points in image plane.
imgpoints = []
# Make a list of calibration images
images = glob.glob('camera_cal/calibration*.jpg')
# Step through the list and search for chessboard corners
for fname in images:
img = cv2.imread(fname)
# Transfer to Gray Scale
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
# Find the chessboard corners
ret, corners = cv2.findChessboardCorners(gray, (nx,ny), None)
# If found, add object points, image points
if ret == True:
objpoints.append(objp)
imgpoints.append(corners)
# Draw and display the corners
cv2.drawChessboardCorners(img, (nx, ny), corners, ret)
# Calibrate camera and calculate mtx and dist parameters
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, gray.shape[::-1], None, None)
dst = cv2.undistort(img, mtx, dist, None, mtx)
# Draw Distorted vs Undistorted images
fig, axes = plt.subplots(nrows = 1,ncols=2, figsize=(20, 10))
axes[0].imshow(img)
axes[0].set_title('Distorted')
axes[1].imshow(dst)
axes[1].set_title('Undistorted')
# Save the camera calibration result for later use
dist_pickle = {}
dist_pickle["mtx"] = mtx
dist_pickle["dist"] = dist
pickle.dump( dist_pickle, open( "camera_cal/camera_cal_pickle.p", "wb" ) )
1 - Apply a distortion correction to raw images using camera calibration matrix and distortion coefficients previously calculated
2 - Calculate Adaptive threshold/Sobel threshold, H, L, S and generate combined image. I found adaptive threshold to be quite productive on the images.
3- Mark the region of interest from the combined image.
4 - Apply a perspective transform to rectify binary image ("birds-eye view").
5 - Detect lane pixels and fit to find the lane boundary.
6 - Determine the curvature of the lane and vehicle position with respect to center.
7 - Warp the detected lane boundaries back onto the original image.
8 - Output visual display of the lane boundaries and numerical estimation of lane curvature and vehicle position.
def Image_Pipeline(img):
bgr_img = cv2.imread(img)
# Convert to RGB Image
rgb_image = cv2.cvtColor(bgr_img, cv2.COLOR_BGR2RGB)
original_image = rgb_image
# Calculate Width and height of image
width = original_image.shape[1]
height = original_image.shape[0]
# Source Points for Image Warp
src_coordinates = np.float32(
[[280, 700], # Bottom left
[595, 460], # Top left
[725, 460], # Top right
[1125, 700]]) # Bottom right
dst_coordinates = np.float32(
[[250, 720], # Bottom left
[250, 0], # Top left
[1065, 0], # Top right
[1065, 720]]) # Bottom right
# Clear mtx and dist and load them from file camera_cal/camera_cal_pickle.p
dist = 0
mtx = 0
# load the camera matrix and distortion coefficient
dist_pickle = pickle.load( open( "camera_cal/camera_cal_pickle.p", "rb" ) )
mtx = dist_pickle["mtx"]
dist = dist_pickle["dist"]
# Undistort the image
undistort_image = cv2.undistort(original_image, mtx, dist, None, mtx)
# use cv2.getPerspectiveTransform() to get M, the transform matrix
M = cv2.getPerspectiveTransform(src_coordinates, dst_coordinates)
# Calculate Adaptive threshold, H, L, S and generate combined image
gray = cv2.cvtColor(undistort_image, cv2.COLOR_RGB2GRAY)
adaptive_Thresh = cv2.adaptiveThreshold(gray,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY,21,2)
hlsBinary_h = hls_threshold(undistort_image, channel='h', thresh=(5, 40))# HLS H-channel Threshold
hlsBinary_l = hls_threshold(undistort_image, channel='l', thresh=(200, 255))# HLS L-channel Threshold
hlsBinary_s = hls_threshold(undistort_image, channel='s', thresh=(90, 255))# HLS S-channel Threshold
# Combine channel thresholds
combined_image = np.zeros_like(hlsBinary_s)
combined_image[((hlsBinary_h == 1) & (hlsBinary_s == 1)) | (hlsBinary_l == 1) | (adaptive_Thresh == 1)] = 1
# Calculate Region of interest
roi_image = region_of_interest(combined_image)
# Use cv2.warpPerspective() to warp your image to a top-down view
warped_image = cv2.warpPerspective(roi_image, M, (width,height), flags=cv2.INTER_LINEAR)
out_img, left_fitx, right_fitx, ploty, left_fit, right_fit = fit_polynomial(warped_image)
Minv = cv2.getPerspectiveTransform(dst_coordinates, src_coordinates)
# Draw the lane lines found
newwarp = draw_lanes(original_image, warped_image,left_fitx,right_fitx, ploty,Minv)
final_image = cv2.addWeighted(undistort_image, 1, newwarp, 0.3, 0)
# Calculate lanes Curvature and car offset
curvead, pos = measure_curvature_pixels(final_image.shape[1], left_fitx, right_fitx, ploty)
# Print the statistics to the image
print_statistics_to_image(final_image, curvead, pos)
f, axes= plt.subplots(1,5,figsize=(15,30))
# Visualize Undistorted Image
axes[0].imshow(undistort_image)
axes[0].set_title("Undistorted Image")
# Visualize Warped Image
axes[1].imshow(combined_image)
axes[1].set_title("Combined Image")
# Visualize Region of Interest
axes[2].imshow(roi_image)
axes[2].set_title("ROI Image")
# Visualize Warped Image
axes[3].imshow(out_img)
axes[3].set_title("Warped Image")
# Visualize Final Image to be saved
axes[4].imshow(final_image)
axes[4].set_title("Final Image")
# Extract name of image to be stored from path
name = os.path.basename(img)
# Convert to BGR
bgr_img = cv2.cvtColor(final_image, cv2.COLOR_RGB2BGR)
# Save warped images to warped_images folder
cv2.imwrite('warped_images/' + name,out_img)
# Save final images to output_images folder
cv2.imwrite('output_images/' + name,bgr_img)
# Reading Images from test_images folder
test_images = glob.glob('test_images/*.jpg')
for fname in test_images:
Image_Pipeline(fname)
def Video_Pipeline(img):
global is_First_Frame
global left_fit
global right_fit
original_image = img
# Calculate Width and height of image
width = original_image.shape[1]
height = original_image.shape[0]
# Source Points for Image Warp
src_coordinates = np.float32(
[[280, 700], # Bottom left
[595, 460], # Top left
[725, 460], # Top right
[1125, 700]]) # Bottom right
dst_coordinates = np.float32(
[[250, 720], # Bottom left
[250, 0], # Top left
[1065, 0], # Top right
[1065, 720]]) # Bottom right
# Clear mtx and dist and load them from file camera_cal/camera_cal_pickle.p
dist = 0
mtx = 0
# load the camera matrix and distortion coefficient
dist_pickle = pickle.load( open( "camera_cal/camera_cal_pickle.p", "rb" ) )
mtx = dist_pickle["mtx"]
dist = dist_pickle["dist"]
# Undistort the image
undistort_image = cv2.undistort(original_image, mtx, dist, None, mtx)
# Use cv2.getPerspectiveTransform() to get M, the transform matrix
M = cv2.getPerspectiveTransform(src_coordinates, dst_coordinates)
# Calculate Adaptive threshold, H, L, S and generate combined image
gray = cv2.cvtColor(undistort_image, cv2.COLOR_RGB2GRAY)
adaptive_Thresh = cv2.adaptiveThreshold(gray,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY,21,2)
hlsBinary_h = hls_threshold(undistort_image, channel='h', thresh=(5, 40))# HLS H-channel Threshold
hlsBinary_l = hls_threshold(undistort_image, channel='l', thresh=(200, 255))# HLS L-channel Threshold
hlsBinary_s = hls_threshold(undistort_image, channel='s', thresh=(90, 255))# HLS S-channel Threshold
# Combine channel thresholds
combined_image = np.zeros_like(hlsBinary_s)
combined_image[((hlsBinary_h == 1) & (hlsBinary_s == 1)) | (hlsBinary_l == 1) | (adaptive_Thresh == 1)] = 1
# Calculate Region of interest
roi_image = region_of_interest(combined_image)
# Use cv2.warpPerspective() to warp your image to a top-down view
warped_image = cv2.warpPerspective(roi_image, M, (width,height), flags=cv2.INTER_LINEAR)
# Fit a polynomial function ( Blind search(First frame) or Search around previously detected lanes)
out_img, left_fitx, right_fitx, ploty, left_fit, right_fit = fit_polynomial(warped_image,is_First_Frame, left_fit, right_fit)
Minv = cv2.getPerspectiveTransform(dst_coordinates, src_coordinates)
# Draw lane lines on the image
newwarp = draw_lanes(original_image, warped_image,left_fitx,right_fitx, ploty,Minv)
final_image = cv2.addWeighted(undistort_image, 1, newwarp, 0.3, 0)
# Calculate Curvature and Car offset from center
curvead, offset = measure_curvature_pixels(final_image.shape[1], left_fitx, right_fitx, ploty)
# Print statistics to the final image
print_statistics_to_image(final_image, curvead, offset)
# Last step clear the is_First_Frame flag we do not need to blindly search for lanes again,
# we can search around the previous detected lanes +/- a margin (hyper parameter)
is_First_Frame = False
return final_image
is_First_Frame = True
left_fit = 0
right_fit = 0
video_output1 = 'project_video_output.mp4'
video_input1 = VideoFileClip('project_video.mp4')#.subclip(0,10)
processed_video = video_input1.fl_image(Video_Pipeline)
%time processed_video.write_videofile(video_output1, audio=False)